package cn.com.infcn.monitor.web;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class Counter implements Serializable {

	public static final String HTTP_COUNTER_NAME = "http";
	public static final String ERROR_COUNTER_NAME = "error";

	public static final int MAX_ERRORS_COUNT = 100;

	private static final int MAX_REQUESTS_COUNT = 10000;
	private static final long serialVersionUID = 6759729262180992976L;
	private String application;
	private boolean displayed = true;
	private transient boolean used;
	private final String name;
	private final boolean errorCounter;
	private final String storageName;
	@SuppressWarnings("all")
	private final ConcurrentMap<String, RequestCounter> requests = new ConcurrentHashMap<String, RequestCounter>();
	@SuppressWarnings("all")
	private final ConcurrentMap<Long, RequestContext> rootCurrentContextsByThreadId = new ConcurrentHashMap<Long, RequestContext>();
	private final LinkedList<IFCCounterError> errors;
	private Date startDate = new Date();
	private int maxRequestsCount = MAX_REQUESTS_COUNT;
	private long estimatedMemorySize;
	private final transient ThreadLocal<RequestContext> contextThreadLocal;

	public Counter(String name) {
		this(name, name, new ThreadLocal<RequestContext>());
	}

	public Counter(String name, String storageName, ThreadLocal<RequestContext> contextThreadLocal) {
		super();
		assert name != null;
		assert storageName != null;
		this.name = name;
		this.storageName = storageName;
		this.errorCounter = ERROR_COUNTER_NAME.equals(name);
		this.contextThreadLocal = contextThreadLocal;
		if (errorCounter) {
			this.errors = new LinkedList<IFCCounterError>();
		} else {
			this.errors = null;
		}
	}

	public void bindContext(String requestName, String completeRequestName, String remoteUser, long startCpuTime) {
		final RequestContext context = new RequestContext(requestName, completeRequestName, remoteUser,
				startCpuTime);
		contextThreadLocal.set(context);
	}

	void setApplication(String application) {
		assert application != null;
		this.application = application;
	}

	String getApplication() {
		return application;
	}

	String getName() {
		return name;
	}

	String getStorageName() {
		return storageName;
	}

	Date getStartDate() {
		return startDate;
	}

	void setStartDate(Date startDate) {
		assert startDate != null;
		this.startDate = startDate;
	}

	boolean isDisplayed() {
		return displayed;
	}

	void setDisplayed(boolean displayed) {
		this.displayed = displayed;
	}

	boolean isUsed() {
		return used;
	}

	void setUsed(boolean used) {
		this.used = used;
	}

	int getMaxRequestsCount() {
		return maxRequestsCount;
	}

	void setMaxRequestsCount(int maxRequestsCount) {
		assert maxRequestsCount > 0;
		this.maxRequestsCount = maxRequestsCount;
	}

	long getEstimatedMemorySize() {
		return estimatedMemorySize;
	}

	boolean isErrorCounter() {
		return errorCounter;
	}

	public void addRequest(String requestName, long duration, long cpuTime, boolean systemError, int responseSize) {
		addRequest(requestName, duration, cpuTime, systemError, null, responseSize);
	}

	private void addRequest(String requestName, long duration, long cpuTime, boolean systemError,
			String systemErrorStackTrace, int responseSize) {

		assert requestName != null;
		assert duration >= 0;
		assert cpuTime >= -1;
		assert responseSize >= -1;

		final String aggregateRequestName = getAggregateRequestName(requestName);

		final RequestContext context = contextThreadLocal.get();
		final RequestCounter request = getCounterRequestInternal(aggregateRequestName);
		synchronized (request) {
			request.addHit(duration, cpuTime, systemError, systemErrorStackTrace, responseSize);

		}
		if (context != null) {
			unbindContext();
		}
		if (systemErrorStackTrace != null) {
			assert errorCounter;
			synchronized (errors) {
				errors.addLast(new IFCCounterError(requestName, systemErrorStackTrace));
				if (errors.size() > MAX_ERRORS_COUNT) {
					errors.removeFirst();
				}
			}
		}
	}

	public void addRequestForSystemError(String requestName, long duration, long cpuTime, String stackTrace) {
		assert requestName != null;
		assert duration >= -1; // -1 pour le counter de log
		assert cpuTime >= -1;
		assert errorCounter;
		assert contextThreadLocal.get() == null;
		final String aggregateRequestName = getAggregateRequestName(requestName);
		final RequestCounter request = getCounterRequestInternal(aggregateRequestName);
		synchronized (request) {
			request.addHit(duration, cpuTime, true, stackTrace, -1);
		}
		synchronized (errors) {
			errors.addLast(new IFCCounterError(requestName, stackTrace));
			if (errors.size() > MAX_ERRORS_COUNT) {
				errors.removeFirst();
			}
		}
	}

	/**
	 * 获取所有的请求信息
	 * 
	 * @return
	 */
	public List<RequestCounter> getRequests() {
		final List<RequestCounter> result = new ArrayList<RequestCounter>(requests.size());
		for (final RequestCounter request : requests.values()) {
			synchronized (request) {
				result.add(request.clone());
			}
		}
		return result;
	}

	List<RequestCounter> getOrderedRequests() {
		final List<RequestCounter> requestList = getRequests();
		if (requestList.size() > 1) {
			Collections.sort(requestList, Collections.reverseOrder(new RequestCounterComparator()));
		}
		return requestList;
	}

	List<RequestCounter> getOrderedByHitsRequests() {
		final List<RequestCounter> requestList = getRequests();
		if (requestList.size() > 1) {
			Collections.sort(requestList, Collections.reverseOrder(new RequestCounterByHitsComparator()));
		}
		return requestList;
	}

	List<RequestContext> getOrderedRootCurrentContexts() {
		final List<RequestContext> contextList = new ArrayList<RequestContext>(
				rootCurrentContextsByThreadId.size());
		for (final RequestContext rootCurrentContext : rootCurrentContextsByThreadId.values()) {
			contextList.add(rootCurrentContext.clone());
		}
		if (contextList.size() > 1) {
			Collections.sort(contextList,
					Collections.reverseOrder(new RequestContextComparator(System.currentTimeMillis())));
		}
		return contextList;
	}

	private RequestCounter getCounterRequestInternal(String requestName) {
		RequestCounter request = requests.get(requestName);
		if (request == null) {
			request = new RequestCounter(requestName, getName());
			final RequestCounter precedentRequest = requests.putIfAbsent(requestName, request);
			if (precedentRequest != null) {
				request = precedentRequest;
			}
		}
		return request;
	}

	public void unbindContext() {
		try {
			contextThreadLocal.remove();
		} finally {
			rootCurrentContextsByThreadId.remove(Thread.currentThread().getId());
		}
	}

	private String getAggregateRequestName(String requestName) {
		return requestName;
	}

	static final class RequestCounterComparator implements Comparator<RequestCounter>, Serializable {
		private static final long serialVersionUID = 1L;

		@Override
		public int compare(RequestCounter request1, RequestCounter request2) {
			if (request1.getDurationsSum() > request2.getDurationsSum()) {
				return 1;
			} else if (request1.getDurationsSum() < request2.getDurationsSum()) {
				return -1;
			} else {
				return 0;
			}
		}
	}

	static final class RequestContextComparator implements Comparator<RequestContext>, Serializable {
		private static final long serialVersionUID = 1L;
		private final long timeOfSnapshot;

		RequestContextComparator(long timeOfSnapshot) {
			super();
			this.timeOfSnapshot = timeOfSnapshot;
		}

		/** {@inheritDoc} */
		@Override
		public int compare(RequestContext context1, RequestContext context2) {
			if (context1.getDuration(timeOfSnapshot) > context2.getDuration(timeOfSnapshot)) {
				return 1;
			} else if (context1.getDuration(timeOfSnapshot) < context2.getDuration(timeOfSnapshot)) {
				return -1;
			} else {
				return 0;
			}
		}
	}

	static final class RequestCounterByHitsComparator implements Comparator<RequestCounter>, Serializable {
		private static final long serialVersionUID = 1L;

		/** {@inheritDoc} */
		@Override
		public int compare(RequestCounter request1, RequestCounter request2) {
			if (request1.getHits() > request2.getHits()) {
				return 1;
			} else if (request1.getHits() < request2.getHits()) {
				return -1;
			} else {
				return 0;
			}
		}
	}
}
